#pragma once

#include <cstddef>
#include <cstdint>
#include <mutex>
#include <string>
#include <string_view>

#ifdef USE_POSIX_SHM
#include <fcntl.h>
using posix_shm_descriptor = struct stat;
using shm_desc_t           = posix_shm_descriptor;
using shmsz_t              = off_t;
#endif
#ifdef USE_WIN32_SHM
#include <Windows.h>
using shmsz_t = unsigned long long;
#endif
namespace shm_kernel::shared_memory {

/**
 * @brief shared memory object status
 *
 */
enum class SHM_STATUS : size_t
{
  /**
   * @brief status is ok.
   * @details functions can be normally called.
   *
   */
  OK = 0,
  /**
   * @brief status is marked as deleted.
   * @details This means that user can no long call any function with that
   * handle. This is usually one of the attached handles has called unlink().
   *
   */
  DEL = 1,
};

/**
 * @brief shared memory object wrapper class
 *
 */
class shm_handle
{

private:
  /**
   * @brief shared memory meta info
   * @details the meta info will be store at the begining of the shared memory
   * object.
   * memory layout might look like this:
   *  | shm_status | ref_count | size | mutex| buffer |
   */
  struct shm_meta_t
  {
    SHM_STATUS shm_status_;
    size_t     ref_count_;
    shmsz_t    shmsz_;
    std::mutex mtx_;
  };

#ifdef USE_POSIX_SHM
  /**
   * @brief shared memory object file descriptor
   * @details this is only availible for POSIX supported platforms. User can use
   * it with posix APIs;
   */
  int fd_;
  /**
   * @brief struct which holds info to describe the shared memory object.
   * @details this is only availible for POSIX supported platforms.
   *
   */
  shm_desc_t status_;
#endif

#ifdef USE_WIN32_SHM
  HANDLE hMapFile_;
#endif
  /**
   * @brief shm_handle's name
   *
   */
  std::string name_;
  /**
   * @brief shared memory buffer ptr.
   *
   */
  void* addr_;

  /**
   * @brief shared memory meta ptr
   *
   */
  shm_meta_t* meta_;

  void update_status(std::error_code& ec) noexcept;
  void update_status() noexcept;
  void unmap_meta() noexcept;
  void unmap_meta(std::error_code& ec) noexcept;

public:
  shm_handle(std::string_view name, const shmsz_t& nbytes);
  shm_handle(std::string_view name);
  shm_handle(const shm_handle&) = delete;
  shm_handle(shm_handle&&) noexcept;
  ~shm_handle();

  void* map() noexcept;
  void* map(void* addr) noexcept;
  void* map(std::error_code& ec) noexcept;
  void* map(void* addr, std::error_code& ec) noexcept;

  void unmap() noexcept;
  void unmap(std::error_code& ec) noexcept;

  void unlink() noexcept;
  void unlink(std::error_code& ec) noexcept;

  const shmsz_t    nbytes() noexcept;
  std::string_view name() noexcept;
  void*            addr() noexcept;
  const size_t&    ref_count() noexcept;
#ifdef USE_WIN32_SHM
  HANDLE native_handle() noexcept;
#endif

#ifdef USE_POSIX_SHM
  const shm_desc_t& status() noexcept;
  /**
   * @see fd_
   * @return const int
   */
  const int fd() noexcept;
#endif
};

}